/*
 * Copyright (c) 2007, Haiku, Inc.
 * Distributed under the terms of the MIT license.
 *
 * Author:
 *		Łukasz 'Sil2100' Zemczak <sil2100@vexillium.org>
 */


#include "PackageInfo.h"

#include <Alert.h>
#include <ByteOrder.h>
#include <FindDirectory.h>
#include <Path.h>
#include <kernel/OS.h>


// Macro reserved for later localization
#define T(x) x

const uint32 kSkipOffset = 33;

// Section constants
enum {
	P_GROUPS_SECTION = 0,
	P_PATH_SECTION,
	P_USER_PATH_SECTION,
	P_LICENSE_SECTION
};


// Element constants
enum {
	P_NONE = 0,
	P_FILE,
	P_DIRECTORY,
	P_LINK
};


PackageInfo::PackageInfo()
	:	
	fStatus(B_NO_INIT),
	fPackageFile(0),
	fDescription(T("No package available.")),
	fProfiles(2),
	fHasImage(false)
{
}


PackageInfo::PackageInfo(const entry_ref *ref)
	:	
	fStatus(B_NO_INIT),
	fPackageFile(new BFile(ref, B_READ_ONLY)),
	fDescription(T("No package selected.")),
	fProfiles(2),
	fHasImage(false)
{
	fStatus = Parse();
}


PackageInfo::~PackageInfo()
{
	pkg_profile *iter = 0;
	while (1) {
		iter = static_cast<pkg_profile *>(fProfiles.RemoveItem((long int)0));
		if (iter)
			delete iter;
		else
			break;
	}

	PkgItem *file = 0;
	while (1) {
		file = static_cast<PkgItem *>(fFiles.RemoveItem((long int)0));
		if (file)
			delete file;
		else
			break;
	}

	if (fPackageFile)
		delete fPackageFile;
}


status_t
PackageInfo::Parse()
{
	// TODO: Clean up
	if (!fPackageFile || fPackageFile->InitCheck() != B_OK) {
		fStatus = B_ERROR;
		return fStatus;
	}
	
	// Check for the presence of the first AlB tag - as the 'magic number'.
	// This also ensures that the file header section is present - which
	// is a crucial pkg section
	char buffer[16];
	fPackageFile->Read(buffer, 8);
	if (buffer[0] != 'A' || buffer[1] != 'l' || buffer[2] != 'B' 
		|| buffer[3] != 0x1a) {
		fStatus = B_ERROR;
		return fStatus;
	}

	fHasImage = false;

	// Parse all known parts of the given .pkg file
	
	uint32 i;
	int8 bytesRead;
	off_t actualSize = 0;
	fPackageFile->GetSize(&actualSize);
	uint64 fileSize = 0;
	
	const char padding[7] = { 0, 0, 0, 0, 0, 0, 0 };

	system_info sysinfo;
	get_system_info(&sysinfo);
	
	uint64 infoOffset = 0, groupsOffset = 0;
	uint64 length = 0;
	
	// Parse the file header
	while (1) {
		bytesRead = fPackageFile->Read(buffer, 7);
		if (bytesRead != 7) {
			fStatus = B_ERROR;
			return fStatus;
		}

		if (!memcmp(buffer, "PhIn", 5)) {
		}
		else if (!memcmp(buffer, "FVer", 5)) {
			// Not used right now
			fPackageFile->Seek(4, SEEK_CUR);
			parser_debug("FVer\n");
		}
		else if (!memcmp(buffer, "AFla", 5)) {
			// Not used right now TODO: Check what this tag is for
			fPackageFile->Seek(8, SEEK_CUR);
			parser_debug("AFla\n");
		}
		else if (!memcmp(buffer, "FSiz", 5)) {
			fPackageFile->Read(&fileSize, 8);
			swap_data(B_UINT64_TYPE, &fileSize, sizeof(uint64),
					B_SWAP_BENDIAN_TO_HOST);
			parser_debug("FSiz %llu\n", fileSize);
		}
		else if (!memcmp(buffer, "COff", 5)) {
			fPackageFile->Read(&infoOffset, 8);
			swap_data(B_UINT64_TYPE, &infoOffset, sizeof(uint64),
					B_SWAP_BENDIAN_TO_HOST);
			parser_debug("COff %llu\n", infoOffset);
		}
		else if (!memcmp(buffer, "AOff", 5)) {
			fPackageFile->Read(&groupsOffset, 8);
			swap_data(B_UINT64_TYPE, &groupsOffset, sizeof(uint64),
					B_SWAP_BENDIAN_TO_HOST);
			parser_debug("AOff %llu\n", groupsOffset);
		}
		else if (!memcmp(buffer, padding, 7)) {
			// This means the end of this section - we should move to the
			// groups section.
			if (groupsOffset) {
				fPackageFile->Seek(groupsOffset, SEEK_SET);
			}
			parser_debug("End!\n");
			break;
		}
		else {
			fStatus = B_ERROR;
			return fStatus;
		}
	}

	fPackageFile->Read(buffer, 7);
	if (memcmp(buffer, "PkgA", 5) || !groupsOffset || !infoOffset) {
		fStatus = B_ERROR;
		return fStatus;
	}
	
	// Section header identifying constant byte sequences:
	const char groupsMarker[7] = { 0, 0, 0, 1, 0, 0, 4 };
	const char idMarker[7] = { 0, 0, 0, 2, 0, 0, 4 };
	const char pathMarker[7] = { 0, 0, 0, 3, 0, 0, 4 };
	const char upathMarker[7] = { 0, 0, 0, 4, 0, 0, 4 };
	const char licenseMarker[7] = { 0, 0, 0, 18, 0, 0, 4 };
	const char descMarker[7] = { 0, 0, 0, 5, 0, 0, 2 };
	const char helpMarker[7] = { 0, 0, 0, 10, 0, 0, 3 };

	const char splashScreenMarker[7] = { 0, 0, 0, 8, 0, 0, 3 };
	const char disclaimerMarker[7] = { 0, 0, 0, 7, 0, 0, 3 };
	
	const char nameMarker[7] = { 0, 0, 0, 13, 0, 0, 2 };
	const char versionMarker[7] = { 0, 0, 0, 14, 0, 0, 2 };
	const char devMarker[7] = { 0, 0, 0, 15, 0, 0, 2 };
	const char shortDescMarker[7] = { 0, 0, 0, 17, 0, 0, 2 };

	int8 section = P_GROUPS_SECTION, installDirectoryFlag = 0;

	pkg_profile group;
	BList groups(3), userPaths(3), systemPaths(10);
	bool groupStarted = false;
	parser_debug("Package Info reached!\n");
	// TODO: Maybe checking whether the needed number of bytes are read
	//	everytime would be a good idea

	// Parse the package info section
	while (1) {
		bytesRead = fPackageFile->Read(buffer, 7);
		if (bytesRead != 7) {
			parser_debug("EOF!\n");
			break;
		}

		if (!memcmp(buffer, groupsMarker, 7)) {
			section = P_GROUPS_SECTION;
			parser_debug("Got to Groups section\n");
			continue;
		}
		else if (!memcmp(buffer, pathMarker, 7)) {
			section = P_PATH_SECTION;
			parser_debug("Got to System Paths\n");
			continue;
		}
		else if (!memcmp(buffer, upathMarker, 7)) {
			section = P_USER_PATH_SECTION;
			parser_debug("Got to User Paths\n");
			continue;
		}
		else if (!memcmp(buffer, licenseMarker, 7)) {
			section = P_LICENSE_SECTION;
			parser_debug("Got to License\n");
			continue;
		} // After this, non sectioned tags follow
		else if (!memcmp(buffer, disclaimerMarker, 7)) {
			uint64 length;
			fPackageFile->Read(&length, 8);
			swap_data(B_UINT64_TYPE, &length, sizeof(uint64), B_SWAP_BENDIAN_TO_HOST);

			uint64 original;
			if (fPackageFile->Read(&original, 8) != 8) {
				fStatus = B_ERROR;
				return fStatus;
			}
			swap_data(B_UINT64_TYPE, &original, sizeof(uint64), B_SWAP_BENDIAN_TO_HOST);

			fPackageFile->Seek(4, SEEK_CUR);

			uint8 *compressed = new uint8[length];
			if (fPackageFile->Read(compressed, length) != static_cast<int64>(length)) {
				fStatus = B_ERROR;
				delete compressed;
				return fStatus;
			}
			
			uint8 *disclaimer = new uint8[original + 1];
			status_t ret = inflate_data(compressed, length, disclaimer, original);
			disclaimer[original] = 0;
			delete compressed;
			if (ret != B_OK) {
				fStatus = B_ERROR;
				delete disclaimer;
				return ret;
			}

			fDisclaimer = (char *)disclaimer;
			delete disclaimer;

			continue;		
		}
		else if (!memcmp(buffer, splashScreenMarker, 7)) {
			uint64 length;
			fPackageFile->Read(&length, 8);
			swap_data(B_UINT64_TYPE, &length, sizeof(uint64), B_SWAP_BENDIAN_TO_HOST);

			uint64 original;
			if (fPackageFile->Read(&original, 8) != 8) {
				fStatus = B_ERROR;
				return fStatus;
			}
			swap_data(B_UINT64_TYPE, &original, sizeof(uint64), B_SWAP_BENDIAN_TO_HOST);

			fPackageFile->Seek(4, SEEK_CUR);

			uint8 *compressed = new uint8[length];
			if (fPackageFile->Read(compressed, length) != static_cast<int64>(length)) {
				fStatus = B_ERROR;
				delete compressed;
				return fStatus;
			}
			
			fImage.SetSize(original);
			status_t ret = inflate_data(compressed, length, 
				static_cast<uint8 *>(const_cast<void *>(fImage.Buffer())), original);
			delete compressed;
			if (ret != B_OK) {
				fStatus = B_ERROR;
				return ret;
			}
			fHasImage = true;
			continue;
		}

		switch (section) {
			case P_PATH_SECTION:
			{
				if (!memcmp(buffer, "DPat", 5)) {
					parser_debug("DPat\n");
					continue;
				}
				else if (!memcmp(buffer, "FDst", 5)) {
					parser_debug("FDst - ");
					directory_which dir;
					if (fPackageFile->Read(&dir, 4) != 4) {
						fStatus = B_ERROR;
						return fStatus;
					}
					swap_data(B_UINT32_TYPE, &dir, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST);
					BPath *path = new BPath();
					status_t ret = find_directory(dir, path);
					if (ret != B_OK) {
						fStatus = B_ERROR;
						return fStatus;
					}

					parser_debug("%s\n", path->Path());

					systemPaths.AddItem(path);
				}
				else if (!memcmp(buffer, "PaNa", 5)) {
					parser_debug("PaNa\n");
					if (fPackageFile->Read(&length, 4) != 4) {
						fStatus = B_ERROR;
						return fStatus;
					}
					swap_data(B_UINT32_TYPE, &length, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST);
					// Since its a default, system path, we can ignore the path name
					// - all information needed is beside the FDst tag.
					fPackageFile->Seek(length, SEEK_CUR);
				}
				else if (!memcmp(buffer, padding, 7)) {
					parser_debug("Padding!\n");
					continue;
				}
				else {
					fStatus = B_ERROR;
					return fStatus;
				}
				break;
			}

			case P_GROUPS_SECTION:
			{
				if (!memcmp(buffer, "IGrp", 5)) {
					// Creata a new group
					groupStarted = true;
					group = pkg_profile();
					parser_debug("IGrp\n");
				}
				else if (!memcmp(buffer, "GrpN", 5)) {
					if (!groupStarted) {
						fStatus = B_ERROR;
						return fStatus;
					}
					
					parser_debug("GrpN\n");
					fPackageFile->Read(&length, 4);
					swap_data(B_UINT32_TYPE, &length, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST);

					char *name = new char[length + 1];
					fPackageFile->Read(name, length);
					name[length] = 0;
					group.name = name;
					delete name;
				}
				else if (!memcmp(buffer, "GrpD", 5)) {
					if (!groupStarted) {
						fStatus = B_ERROR;
						return fStatus;
					}
					
					parser_debug("GrpD\n");
					fPackageFile->Read(&length, 4);
					swap_data(B_UINT32_TYPE, &length, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST);

					char *desc = new char[length + 1];
					fPackageFile->Read(desc, length);
					desc[length] = 0;
					group.description = desc;
					delete desc;
				}
				else if (!memcmp(buffer, "GrHt", 5)) {
					if (!groupStarted) {
						fStatus = B_ERROR;
						return fStatus;
					}

					parser_debug("GrHt\n");
					// For now, we don't need group help
					fPackageFile->Read(&length, 4);
					swap_data(B_UINT32_TYPE, &length, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST);
					fPackageFile->Seek(length, SEEK_CUR);
				}
				else if (!memcmp(buffer, padding, 5)) {
					if (!groupStarted) {
						parser_debug("No group - padding!\n");
						continue;
					}

					fProfiles.AddItem(new pkg_profile(group));
					parser_debug("Group added: %s %s\n", group.name.String(), 
							group.description.String());
					
					groupStarted = false;
				}
				else if (!memcmp(buffer, "GrId", 5)) {
					uint32 id;
					fPackageFile->Read(&id, 4);
					swap_data(B_UINT32_TYPE, &id, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST);

					parser_debug("GrId\n");
					
					if (id == 0xffffffff)
						groups.AddItem(NULL);
					else
						groups.AddItem(fProfiles.ItemAt(id));
				}
				else if (!memcmp(buffer, idMarker, 7) || 
						!memcmp(buffer, groupsMarker, 7)) {
					parser_debug("Marker, jumping!\n");
					continue;
				}
				else {
					fStatus = B_ERROR;
					return fStatus;
				}
				break;
			}

			case P_LICENSE_SECTION:
			{
				if (!memcmp(buffer, "Lic?", 5)) {
					parser_debug("Lic?\n");
					// This tag informs whether a license is present in the package
					// or not. Since we don't care about licenses right now, just
					// skip this section
					fPackageFile->Seek(4, SEEK_CUR);
				}
				else if (!memcmp(buffer, "LicP", 5)) {
					parser_debug("LicP\n");
					fPackageFile->Read(&length, 4);
					swap_data(B_UINT32_TYPE, &length, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST);
					
					fPackageFile->Seek(length, SEEK_CUR);
				}
				else if (!memcmp(buffer, padding, 7)) {
					continue;
				}
				else if (!memcmp(buffer, descMarker, 7)) {
					parser_debug("Description text reached\n");
					fPackageFile->Read(&length, 4);
					swap_data(B_UINT32_TYPE, &length, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST);

					char *description = new char[length + 1];
					fPackageFile->Read(description, length);
					description[length] = 0;
					fDescription = description;

					// Truncate all leading newlines
					for (i = 0;i < length;i++)
						if (fDescription[i] != '\n')
							break;
					fDescription.Remove(0, i);

					delete description;
					parser_debug("Description text reached\n");
					
					// After this, there's a known size sequence of bytes, which meaning
					// is yet to be determined.

					// One is already known. The byte (or just its least significant bit)
					// at offset 21 from the description text is responsible for the
					// install folder existence information. If it is 0, there is no
					// install folder, if it is 1 (or the least significant bit is set)
					// it means we should install all 0xffffffff files/directories to
					// the first directory existing in the package
					fPackageFile->Seek(21, SEEK_CUR);
					if (fPackageFile->Read(&installDirectoryFlag, 1) != 1) {
						fStatus = B_ERROR;
						return fStatus;
					}

					fPackageFile->Seek(11, SEEK_CUR);
				}
				else if (!memcmp(buffer, nameMarker, 7)) {
					parser_debug("Package name reached\n");
					fPackageFile->Read(&length, 4);
					swap_data(B_UINT32_TYPE, &length, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST);

					char *name = new char[length + 1];
					fPackageFile->Read(name, length);
					name[length] = 0;
					fName = name;
					delete name;
				}
				else if (!memcmp(buffer, versionMarker, 7)) {
					parser_debug("Package version reached\n");
					fPackageFile->Read(&length, 4);
					swap_data(B_UINT32_TYPE, &length, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST);

					char *version = new char[length + 1];
					fPackageFile->Read(version, length);
					version[length] = 0;
					fVersion = version;
					delete version;
				}
				else if (!memcmp(buffer, devMarker, 7)) {
					parser_debug("Package developer reached\n");
					fPackageFile->Read(&length, 4);
					swap_data(B_UINT32_TYPE, &length, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST);

					char *dev = new char[length + 1];
					fPackageFile->Read(dev, length);
					dev[length] = 0;
					fDeveloper = dev;
					delete dev;
				}
				else if (!memcmp(buffer, shortDescMarker, 7)) {
					parser_debug("Package short description reached\n");
					fPackageFile->Read(&length, 4);
					swap_data(B_UINT32_TYPE, &length, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST);

					char *desc = new char[length + 1];
					fPackageFile->Read(desc, length);
					desc[length] = 0;
					fShortDesc = desc;
					delete desc;
				}
				else if (!memcmp(buffer, helpMarker, 7)) {
					// The help text is a stored in deflated state, preceded by a 64 bit
					// compressed size, 64 bit inflated size and a 32 bit integer
					// Since there was no discussion whether we need this help text,
					// it will be skipped
					parser_debug("Help text reached\n");
					//uint64 length64;
					fPackageFile->Read(&length, 8);
					swap_data(B_UINT64_TYPE, &length, sizeof(uint64), B_SWAP_BENDIAN_TO_HOST);
					
					fPackageFile->Seek(12 + length, SEEK_CUR);
				}
				break;
			}

			case P_USER_PATH_SECTION:
			{
				if (!memcmp(buffer, "DPat", 5)) {
					parser_debug("DPat\n");
					continue;
				}
				else if (!memcmp(buffer, "PaNa", 5)) {
					parser_debug("PaNa\n");
					fPackageFile->Read(&length, 4);
					swap_data(B_UINT32_TYPE, &length, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST);
					
					char *pathname = new char[length + 1];
					fPackageFile->Read(pathname, length);
					pathname[length] = 0;
					BString *path = new BString(pathname);
					userPaths.AddItem(path);
					delete pathname;
				}
				else if (!memcmp(buffer, padding, 7)) {
					parser_debug("Padding!\n");
					continue;
				}
				else {
					fStatus = B_ERROR;
					return fStatus;
				}
				break;
			}
		}
	}

	BString nameString, mimeString, signatureString, linkString;
	BString itemPath = "", installDirectory = "";
	uint32 directoryCount = 0;

	uint8 element = P_NONE;
	uint32 itemGroups = 0, path = 0, cust = 0, ctime = 0, mtime = 0, 
				 platform = 0xffffffff;
	uint64 offset = 0, size = 0, originalSize = 0, mode = 0;
	uint8 pathType = P_INSTALL_PATH;

	status_t ret;

	fPackageFile->Seek(infoOffset, SEEK_SET);

	// Parse package file data
	while (1) {
		bytesRead = fPackageFile->Read(buffer, 7);
		if (bytesRead != 7) {
			fStatus = B_ERROR;
			return fStatus;
		}

		// TODO: Here's the deal... there seems to be a strange ScrI tag that
		//		seems to mean script files (check this). It seems exaclty the same
		//		as a normal file (just as script files are normal files) so for
		//		now I'm treating those as files. Check if it's correct!
		//		No, it isn't and I will fix this soon.
		if (!memcmp(buffer, "FilI", 5) || !memcmp(buffer, "ScrI", 5)) {
			parser_debug("FilI\n");
			element = P_FILE;
			
			mimeString = "";
			nameString = "";
			signatureString = "";

			itemGroups = 0;
			ctime = 0;
			mtime = 0;
			offset = 0;
			itemGroups = 0;
			cust = 0;
			mode = 0;
			platform = 0xffffffff;

			size = 0;
			originalSize = 0;
		}
		else if (!memcmp(buffer, "FldI", 5)) {
			parser_debug("FldI\n");
			element = P_DIRECTORY;
			
			nameString = "";

			itemGroups = 0;
			ctime = 0;
			mtime = 0;
			offset = 0;
			itemGroups = 0;
			cust = 0;
			platform = 0xffffffff;
			
			size = 0;
			originalSize = 0;
		}
		else if (!memcmp(buffer, "LnkI", 5)) {
			parser_debug("LnkI\n");
			element = P_LINK;

			nameString = "";
			linkString = "";

			itemGroups = 0;
			ctime = 0;
			mtime = 0;
			offset = 0;
			itemGroups = 0;
			cust = 0;
			platform = 0xffffffff;
			
			size = 0;
			originalSize = 0;
		}
		else if (!memcmp(buffer, "Name", 5)) {
			if (element == P_NONE) {
				fStatus = B_ERROR;
				return fStatus;
			}

			parser_debug("Name\n");
			fPackageFile->Read(&length, 4);
			swap_data(B_UINT32_TYPE, &length, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST);

			char *name = new char[length + 1];
			fPackageFile->Read(name, length);
			name[length] = 0;

			nameString = name;
			delete name;
		}
		else if (!memcmp(buffer, "Grps", 5)) {
			if (element == P_NONE) {
				fStatus = B_ERROR;
				return fStatus;
			}
			
			parser_debug("Grps\n");
			fPackageFile->Read(&itemGroups, 4);
			swap_data(B_UINT32_TYPE, &itemGroups, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST);
		}
		else if (!memcmp(buffer, "Dest", 5)) {
			if (element == P_NONE) {
				fStatus = B_ERROR;
				return fStatus;
			}

			parser_debug("Dest\n");
			fPackageFile->Read(&path, 4);
			swap_data(B_UINT32_TYPE, &path, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST);
		}
		else if (!memcmp(buffer, "Cust", 5)) {
			if (element == P_NONE) {
				fStatus = B_ERROR;
				return fStatus;
			}

			parser_debug("Cust\n");
			fPackageFile->Read(&cust, 4);
			swap_data(B_UINT32_TYPE, &cust, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST);
		}
		else if (!memcmp(buffer, "Repl", 5)) {
			if (element == P_NONE) {
				fStatus = B_ERROR;
				return fStatus;
			}

			parser_debug("Repl\n");
			fPackageFile->Seek(4, SEEK_CUR);
			// TODO: Should the replace philosophy depend on this flag? For now
			//	I always leave the decision to the user
		}
		else if (!memcmp(buffer, "Plat", 5)) {
			if (element == P_NONE) {
				fStatus = B_ERROR;
				return fStatus;
			}
			
			parser_debug("Plat\n");
			fPackageFile->Read(&platform, 4);
			swap_data(B_UINT32_TYPE, &platform, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST);
		}
		else if (!memcmp(buffer, "CTim", 5)) {
			if (element == P_NONE) {
				fStatus = B_ERROR;
				return fStatus;
			}

			parser_debug("CTim\n");
			fPackageFile->Read(&ctime, 4);
			swap_data(B_UINT32_TYPE, &ctime, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST);
		}
		else if (!memcmp(buffer, "MTim", 5)) {
			if (element == P_NONE) {
				fStatus = B_ERROR;
				return fStatus;
			}

			parser_debug("MTim\n");
			fPackageFile->Read(&mtime, 4);
			swap_data(B_UINT32_TYPE, &mtime, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST);
		}
		else if (!memcmp(buffer, "OffT", 5)) {
			if (element == P_NONE) {
				fStatus = B_ERROR;
				return fStatus;
			}

			parser_debug("OffT\n");
			fPackageFile->Read(&offset, 8);
			swap_data(B_UINT64_TYPE, &offset, sizeof(uint64), B_SWAP_BENDIAN_TO_HOST);
		}
		else if (!memcmp(buffer, "Mime", 5)) {
			if (element != P_FILE) {
				fStatus = B_ERROR;
				return fStatus;
			}

			fPackageFile->Read(&length, 4);
			swap_data(B_UINT32_TYPE, &length, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST);
			
			char *mime = new char[length + 1];
			fPackageFile->Read(mime, length);
			mime[length] = 0;
			parser_debug("Mime: %s\n", mime);

			mimeString = mime;
			delete mime;
		}
		else if (!memcmp(buffer, "CmpS", 5)) {
			if (element == P_NONE) {
				fStatus = B_ERROR;
				return fStatus;
			}

			parser_debug("CmpS\n");
			fPackageFile->Read(&size, 8);
			swap_data(B_UINT64_TYPE, &size, sizeof(uint64), B_SWAP_BENDIAN_TO_HOST);
		}
		else if (!memcmp(buffer, "OrgS", 5)) {
			if (element != P_FILE && element != P_LINK) {
				fStatus = B_ERROR;
				return fStatus;
			}

			parser_debug("OrgS\n");
			fPackageFile->Read(&originalSize, 8);
			swap_data(B_UINT64_TYPE, &originalSize, sizeof(uint64), B_SWAP_BENDIAN_TO_HOST);
		}
		else if (!memcmp(buffer, "VrsI", 5)) {
			if (element != P_FILE) {
				fStatus = B_ERROR;
				return fStatus;
			}
			
			parser_debug("VrsI\n");
			fPackageFile->Seek(24, SEEK_CUR);
			// TODO
			// Also, check what those empty 20 bytes mean
		}
		else if (!memcmp(buffer, "Mode", 5)) {
			if (element != P_FILE && element != P_LINK) {
				fStatus = B_ERROR;
				return fStatus;
			}
			
			parser_debug("Mode\n");
			fPackageFile->Read(&mode, 4);
			swap_data(B_UINT32_TYPE, &mode, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST);
		}
		else if (!memcmp(buffer, "FDat", 5)) {
			if (element != P_DIRECTORY) {
				fStatus = B_ERROR;
				return fStatus;
			}
			
			parser_debug("FDat\n");
		}
		else if (!memcmp(buffer, "ASig", 5)) {
			if (element != P_FILE) {
				fStatus = B_ERROR;
				return fStatus;
			}

			fPackageFile->Read(&length, 4);
			swap_data(B_UINT32_TYPE, &length, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST);
			
			char *signature = new char[length + 1];
			fPackageFile->Read(signature, length);
			signature[length] = 0;
			parser_debug("Signature: %s\n", signature);

			signatureString = signature;
			delete signature;
		}
		else if (!memcmp(buffer, "Link", 5)) {
			if (element != P_LINK) {
				fStatus = B_ERROR;
				return fStatus;
			}

			fPackageFile->Read(&length, 4);
			swap_data(B_UINT32_TYPE, &length, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST);
			
			char *link = new char[length + 1];
			fPackageFile->Read(link, length);
			link[length] = 0;
			parser_debug("Link: %s\n", link);

			linkString = link;
			delete link;
		}
		else if (!memcmp(buffer, padding, 7)) {
			PkgItem *item = 0;
			
			parser_debug("Padding!\n");
			if (platform != 0xffffffff && 
					static_cast<platform_types>(platform) != sysinfo.platform_type) {
				// If the file/directory/item's platform is different than the
				// target platform (or different than the 'any' constant), ignore
				// this file
			}
			else if (element == P_FILE) {
				if (itemGroups && offset && size) {
					BString dest = "";
					uint8 localType = pathType;
					
					if (path == 0xfffffffe) 
						dest << itemPath << "/" << nameString.String();
					else if (path == 0xffffffff) {
						localType = P_INSTALL_PATH;
						dest = installDirectory;
						dest << nameString;
					}
					else {
						if (cust) {
							BString *def = static_cast<BString *>(userPaths.ItemAt(path));
							if (!def) {
								fStatus = B_ERROR;
								return fStatus;
							}
							if ((*def)[0] == '/')
								localType = P_SYSTEM_PATH;
							else
								localType = P_USER_PATH;
							
							dest << *def << "/" << nameString;
						}
						else {
							BPath *def = static_cast<BPath *>(systemPaths.ItemAt(path));
							if (!def) {
								fStatus = B_ERROR;
								return fStatus;
							}
							localType = P_SYSTEM_PATH;

							dest << def->Path() << "/" << nameString;
						}
					}

					item = new PkgFile(fPackageFile, dest, localType, ctime, mtime, 
							offset, size, originalSize, 0, mimeString, signatureString, mode);
					parser_debug("Adding file: %s!\n", dest.String());
				}
			}
			else if (element == P_DIRECTORY) {
				if (itemGroups) { 
					if (installDirectoryFlag != 0) {
						if (installDirectoryFlag < 0) { // Normal directory
							if (path == 0xfffffffe) { // Install to current directory
								itemPath << "/" << nameString.String();
								directoryCount++;
							}
							else if (path == 0xffffffff) { // Install to install directory
								pathType = P_INSTALL_PATH;
								itemPath = installDirectory;
								itemPath << nameString;
								directoryCount = 1;
							}
							else { // Install to defined directory
								if (cust) {
									BString *def = static_cast<BString *>(userPaths.ItemAt(path));
									if (!def) {
										fStatus = B_ERROR;
										return fStatus;
									}
									if ((*def)[0] == '/')
										pathType = P_SYSTEM_PATH;
									else
										pathType = P_USER_PATH;
		
									itemPath = *def;
								}
								else {
									BPath *def = static_cast<BPath *>(systemPaths.ItemAt(path));
									if (!def) {
										fStatus = B_ERROR;
										return fStatus;
									}
									pathType = P_SYSTEM_PATH;
									
									itemPath = def->Path();
								}
		
								itemPath << "/" << nameString;
								directoryCount = 1;
							}
						}
						else { // Install directory
							if (path != 0xffffffff) {
								fStatus = B_ERROR;
								return fStatus;
							}
	
							installDirectory = nameString;
							installDirectory << "/";
							pathType = P_INSTALL_PATH;
							itemPath = nameString;
	
							installDirectoryFlag = -1;
						}
					
						parser_debug("Adding the directory %s!\n", itemPath.String());
						item = new PkgDirectory(fPackageFile, itemPath, pathType, ctime, 
							mtime, offset, size);
					}
					else {
						installDirectoryFlag = -1;
					}
				}
			}
			else if (element == P_LINK) {
				if (itemGroups && linkString.Length()) {
					BString dest = "";
					uint8 localType = pathType;
					
					if (path == 0xfffffffe) 
						dest << itemPath << "/" << nameString.String();
					else if (path == 0xffffffff) {
						localType = P_INSTALL_PATH;
						dest = installDirectory;
						dest << nameString;
					}
					else {
						if (cust) {
							BString *def = static_cast<BString *>(userPaths.ItemAt(path));
							if (!def) {
								fStatus = B_ERROR;
								return fStatus;
							}
							if ((*def)[0] == '/')
								localType = P_SYSTEM_PATH;
							else
								localType = P_USER_PATH;
							
							dest << *def << "/" << nameString;
						}
						else {
							BPath *def = static_cast<BPath *>(systemPaths.ItemAt(path));
							if (!def) {
								fStatus = B_ERROR;
								return fStatus;
							}
							localType = P_SYSTEM_PATH;

							dest << def->Path() << "/" << nameString;
						}
					}

					parser_debug("Adding link: %s!\n", dest.String());
					item = new PkgLink(fPackageFile, dest, linkString, pathType,
						ctime, mtime, mode, offset, size);
				}
			}
			else {
				// If the directory tree count is equal to zero, this means all
				// directory trees have been closed and a padding sequence means the
				// end of the section
				if (directoryCount == 0)
					break;
				ret = itemPath.FindLast('/');
				if (ret == B_ERROR) {
					itemPath = "";
				}
				else {
					itemPath.Truncate(ret);
				}
				directoryCount--;
			}

			if (item) {
				_AddItem(item, originalSize, itemGroups, path, cust);
			}

			element = P_NONE;
		}
		else {
			fStatus = B_ERROR;
			return fStatus;
		}
	}

	if (static_cast<uint64>(actualSize) != fileSize) {
		// Inform the user of a possible error
		int32 selection;
		BAlert *warning = new BAlert(T("filesize_wrong"),
			T("There seems to be a filesize mismatch in the package file. "
				"The package might be corrupted or have been modified after its "
				"creation. Do you still wish to continue?"), T("Yes"), T("No"), NULL, 
			B_WIDTH_AS_USUAL, B_WARNING_ALERT);
		selection = warning->Go();
		
		if (selection == 1) {
			fStatus = B_ERROR;
			return fStatus;
		}
	}

	if (!groups.IsEmpty())
		fProfiles = groups;

	return B_OK;
}


void
PackageInfo::_AddItem(PkgItem *item, uint64 size, uint32 groups, uint32 path, 
		uint32 cust)
{
	// Add the item to all groups it resides in
	uint32 i, n = fProfiles.CountItems(), mask = 1;
	pkg_profile *profile;
					
	for (i = 0;i < n;i++) {
		if (groups & mask) {
			profile = static_cast<pkg_profile *>(fProfiles.ItemAt(i));
			profile->items.AddItem(item);
			profile->space_needed += size;
			// If there is at least one non-predefined destination element
			// in the package, we give the user the ability to select the
			// installation directory.
			// If there are only predefined path files in the package, but
			// such defined by the user, the user will be able to select
			// the destination volume
			if (path == 0xffffffff)
				profile->path_type = P_INSTALL_PATH;
			else if (path < 0xfffffffe && 
					profile->path_type != P_INSTALL_PATH) {
				if (cust) {
					profile->path_type = P_USER_PATH;
				}
			}
		}
		mask = mask << 1;
	}
}


//	#pragma mark -


pkg_profile::~pkg_profile()
{
	PkgItem *iter = 0;
	while (1) {
		iter = static_cast<PkgItem *>(items.RemoveItem((long int)0));
		if (iter)
			delete iter;
		else
			break;
	}
}

